//UDP网络通信
#ifndef SERVER
#define SERVER

#include <iostream>
#include <unordered_map>
#include <string>
#include <cstdio>
#include <cerrno> //使用errno(错误码)
#include <cstring> //使用strerror(errno):将错误码转为字符串
#include <cstdlib> //exit()
#include <strings.h>//bzero() :将内存块的前n个字节清零

#include <sys/types.h>  //是Unix/Linux系统的基本系统数据类型的头文件，socket()
#include <sys/socket.h> //socket(): 创建socket_fd ; inet_addr(): 将IP字符串转为4字节整型。
#include <netinet/in.h> //struct sockaddr_in ; inet_addr()
#include <arpa/inet.h>  //htons()、htonl() ：将主机数据转为网络数据 ; inet_addr()
#include <unistd.h>     //close(): 关闭socket_fd。

#include "log.hpp"

using std::string;
using std::cout;
using std::endl;
using std::cin;

#define BUF_SIZE 1024 

class UdpServer
{
public:
    UdpServer(uint16_t port, string ip="")
    :port_(port),ip_(ip),sock_fd_(-1)
    {

    }
    ~UdpServer()
    {
        if(sock_fd_ > 0) close(sock_fd_);
    }
//服务器类需要提供的接口：
    //1.初始化服务器
    bool initServer()
    {
        //在这里就要使用网络调用来实现性能了。
        //1.1 创建套接字文件描述符（socket_fd）
        sock_fd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock_fd_ < 0) //如果套接字创建失败
        {
            logMessage(FATAL, "%d:%s:%s\n", errno, strerror(errno), "创建套接字失败！"); //strerror(errno)：将错误码转为字符串。
            exit(2);
        }
        logMessage(NORMAL, "1.socket_fd created successfully。\n");

        //1.2 创建一个sockaddr_in
        //a. 定义sockaddr_in结构体。
        struct sockaddr_in local;
        //b. 初始化内存块为0 。
        bzero(&local, sizeof(local));//用来初始化：将内存块(&local)的前n个字节(sizeof(local))清零，也可使用memset
        //c. 设置地址类型。
        local.sin_family = AF_INET; 
        //d. 设置端口号。（注意大小端）
        local.sin_port = htons(port_);//端口号要被对方获取，也是网络数据的一部分，所以要考虑大小端问题。
        /*e. 设置IP地址。（注意大小端）
            //e.1 将点分十进制字符串风格的IP地址 -> 4字节整数
            //e.2 将4字节整数 -> 网络序列
            //有一个接口，可以直接完成上面两步：inet_addr()*/
            //INADDR_ANY : 让进程可以在网络通信时，从任意IP获取数据，因为一台主机可以有多个IP（每多一张网卡，就多一个IP）(但进程的端口号是确定的)
        local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());//C接口不接受string，用类方法c_str()转为const char*
        
        //1.3 socket_fd绑定sockaddr_in(里面有IP地址与端口号)
        if(bind(sock_fd_, (sockaddr*)(&local), sizeof(local)) < 0)//因为接口是通用的，使用sockaddr_in必须强转为通用的sockaddr。
        {
            logMessage(FATAL, "%d:%s:%s\n", errno, strerror(errno), "Failed to create socket！"); //strerror(errno)：将错误码转为字符串。
            exit(2);
        }
        logMessage(NORMAL, "2.sockaddr_in created successfully.\n");

        return true;
    }

    //2.启动服务器(网络服务器是要一直运行，不主动退出的。)
    void Start()
    {
        logMessage(NORMAL, "3.Preparing to read data.\n");
        char buffer[BUF_SIZE] = {0};//缓冲区，用于recvfrom()来接收信息

        //char result[256];//获取文件中的命令执行结果
        //std::string cmd_result; //文件中的命令执行结果
        while(1) //服务器启动后，该进程是常驻进程不主动退出，所以要小心内存泄漏。
        {
            //2.1 读取数据
            struct sockaddr_in peer; //用于记录发送方的sockaddr
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);//输入&输出型参数，输入用来接收sockaddr的参数的大小，输出实际读到的大小
            ssize_t s = recvfrom(sock_fd_, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)(&peer), &len);
            if(s < 0) //数据读取失败打印提示信息
            {
                logMessage(FATAL, "%d:%s:%s\n", errno, strerror(errno), "创建套接字失败！"); //strerror(errno)：将错误码转为字符串。
            }

            //数据读取成功
            //2.2 分析和处理数据
                buffer[s] = 0;
                //cmd_result.clear();
            //版本三：实现一个广播（群聊、世界频道）（将消息发送给所有客户端）
                uint16_t cli_port = ntohs(peer.sin_port);//发送方进程的端口号(从网络中获取的数据要注意大小端)
                string cli_ip = inet_ntoa(peer.sin_addr);//1.注意大小端 2.化为点分十进制字符串
                char key[64]; //保存群聊用户名（IP-port）
                snprintf(key, sizeof(key), "%s-%u", cli_ip.c_str(), cli_port);//不应该使用printf输出一个String类型的字符串
                auto it = users_.find(key);//如果无序图中没有该键值对，插入
                if(it == users_.end())
                {
                    logMessage(NORMAL, "add new user : %s\n", key);
                    users_.insert({key, peer});
                }
            //版本二：实现一个类似于Xshell的程序（远程获取指令，执行该指令）
                // if(strcasestr(buffer, "rm") != nullptr) //strcasestr():查找子串
                // {
                //     cmd_result = "您存在违规操作，请注意！\n";
                //     std::cout << "违规操作：" << buffer << std::endl;
                //     sendto(sock_fd_, cmd_result.c_str(), cmd_result.size(), 0, (struct sockaddr*)(&peer), len);
                //     continue;
                // }
                // FILE *fp = popen(buffer, "r"); //执行命令，并将结果放在文件中
                // if(fp == nullptr)
                // {
                //     logMessage(ERROR, "popen: %d:%s", errno, strerror(errno));
                //     continue;
                // }
                // while(fgets(result, sizeof(result), fp) != nullptr)
                // {
                //     cmd_result += result;
                // }

            //版本一：获取信息，输出信息
                // //a.输出接收到的信息
                // //b.获取发送方进程的信息(从网络中获取的数据要注意大小端)
                // uint16_t cli_port = ntohs(peer.sin_port);//发送方进程的端口号(从网络中获取的数据要注意大小端)
                // string cli_ip = inet_ntoa(peer.sin_addr);//1.注意大小端 2.化为点分十进制字符串
                // printf("[IP: %s][port: %d] send message : %s\n", cli_ip.c_str(), cli_port, buffer);

            //2.3 写回数据
            //sendto(sock_fd_, buffer, strlen(buffer), 0, (struct sockaddr*)(&peer), len);
            //sendto(sock_fd_, cmd_result.c_str(), cmd_result.size(), 0, (struct sockaddr*)(&peer), len);
            for(auto &iter : users_)
            {
                std::string sendMessage = key;
                sendMessage += "#";
                sendMessage += buffer;
                logMessage(NORMAL, "push message to %s\n", iter.first.c_str());
                sendto(sock_fd_, sendMessage.c_str(), sendMessage.size(), 0, (struct sockaddr *)&iter.second, sizeof(iter.second));
            }
        }
    }
private:
    //一个服务器，一般必须要ip地址和port(端口号：16位的整数)
    uint16_t port_;
    string ip_;
    int sock_fd_; //套接字文件描述符
    std::unordered_map<std::string, struct sockaddr_in> users_;//记录所有群聊成员
};



#endif